package de.mmis.core.base.device;

import org.apache.log4j.Logger;

/**
 * abstract {@link Device} implementation implementing availability methods.
 * 
 * @author Christian Koch
 * 
 * @param <D>
 */
public abstract class AbstractDevice<D> implements Device<D> {

	private final static Logger LOGGER = Logger.getLogger(AbstractDevice.class);

	private Object availabilityMutex;
	private int numReferences;

	/**
	 * creates new {@link AbstractDevice}
	 * 
	 * @param enabled
	 *            if set to <code>true</code>, the device is initially enabled,
	 *            otherwise it is disabled
	 */
	public AbstractDevice(boolean enabled) {
		this.availabilityMutex = new Object();
		this.numReferences = enabled ? 0 : -1;
	}

	@Override
	public void ensureAvailability() {
		synchronized (this.availabilityMutex) {
			while (this.numReferences == -1) {
				try {
					this.availabilityMutex.wait();
				} catch (InterruptedException e) {
					LOGGER.error("mutex wait interrupted unexpectedly", e);
				}
			}
			this.numReferences++;
			this.availabilityMutex.notifyAll();
		}
	}

	@Override
	public void releaseAvailability() {
		synchronized (this.availabilityMutex) {
			if (this.numReferences > 0)
				this.numReferences--;
			this.availabilityMutex.notifyAll();
		}
	}

	@Override
	public boolean tryEnsureAvailability() {
		synchronized (this.availabilityMutex) {
			if (this.numReferences == -1)
				return false;
			this.numReferences++;
			this.availabilityMutex.notifyAll();
			return true;
		}
	}

	/**
	 * enables the device
	 * 
	 * @return <code>true</code>, if the device was previously disabled,
	 *         otherwise <code>false</code>
	 */
	protected boolean enableDevice() {
		boolean statusChanged = false;
		synchronized (this.availabilityMutex) {
			if (this.numReferences == -1) {
				this.numReferences = 0;
				statusChanged = true;
			}
			this.availabilityMutex.notifyAll();
		}
		return statusChanged;
	}

	/**
	 * disables the device gracefully, removing all availability grants by force
	 * after a given time span
	 * 
	 * @param millisecondsUntilForce
	 *            time span after which the device will be disabled even if
	 *            availability is ensured to some clients
	 * @return <code>true</code>, if the device was previously enabled,
	 *         otherwise <code>false</code>
	 */
	protected boolean disableDevice(long millisecondsUntilForce) {
		long millisUntilForce = millisecondsUntilForce;
		boolean statusChanged = false;
		if (millisUntilForce < 0)
			millisUntilForce = Long.MAX_VALUE;
		synchronized (this.availabilityMutex) {
			if (this.numReferences > 0) {
				try {
					this.availabilityMutex.wait(millisUntilForce);
				} catch (InterruptedException e) {
					LOGGER.error("mutex wait interrupted unexpectedly", e);
				}
			}
			if (this.numReferences > 0)
				LOGGER.warn("WARNING: forcing disable of device "
						+ getID()
						+ ". This may happen because a GUI component forgot to call releaseAvailability method");
			if (this.numReferences >= 0) {
				this.numReferences = -1;
				statusChanged = true;
			}
			this.availabilityMutex.notifyAll();
		}
		return statusChanged;
	}
}
